目录
  1. 1. 一、Android 中的 Linux 系统调用
    1. 1.1. 1.1 系统调用的底层细节
  2. 2. 二、文件 I/O:系统调用 vs C 库
    1. 2.1. 2.1 无缓冲 I/O(系统调用)
    2. 2.2. 2.2 缓冲 I/O(C 标准库)
    3. 2.3. 2.3 实用技巧:mmap 文件映射
    4. 2.4. 2.4 mmap 的深入:MAP_SHARED vs MAP_PRIVATE vs MAP_ANONYMOUS
  3. 3. 三、进程管理:Zygote 模型
    1. 3.1. 3.1 posix_spawn: fork + exec 的安全替代
  4. 4. 四、信号处理
    1. 4.1. 4.1 signalfd:将信号变成文件描述符
  5. 5. 五、epoll:高效 I/O 多路复用
    1. 5.1. 5.1 timerfd 和 eventfd
  6. 6. 六、Unix Domain Socket
    1. 6.1. 6.1 SCM_RIGHTS:通过 Unix Socket 传递文件描述符
  7. 7. 七、匿名共享内存(ashmem)
    1. 7.1. 7.1 System V IPC vs POSIX IPC
  8. 8. 八、CMake 构建配置
  9. 9. 九、面试常问题目
【C/C++理论实战技术】Linux编程实战

一、Android 中的 Linux 系统调用

Android 基于 Linux 内核,所有底层操作最终都通过系统调用(syscall)完成。NDK 开发中可以直接使用 POSIX API,这些 API 内部封装了系统调用:

类别 系统调用 函数封装 AOSP 使用场景
文件 I/O open/read/write/close fopen/fread/fwrite/fclose 文件读写
进程管理 fork/execve/waitpid fork/exec/wait Zygote 进程孵化
内存管理 mmap/munmap/brk malloc/free(内部调用) Binder 内存映射、匿名共享内存
线程同步 futex/clone pthread_create/pthread_mutex_lock 所有线程操作
网络 I/O socket/bind/connect/send/recv getaddrinfo/connect 网络通信
设备控制 ioctl OS 封装函数 HAL 层与硬件通信
文件监控 inotify_add_watch FileObserver(Java API) 文件变化监听

Android 的系统调用表在 bionic/libc/kernel/uapi/asm-generic/unistd.h 中定义。32 位 ARM 架构使用 __NR_ 前缀(如 __NR_openat),通过 svc #0 指令触发;64 位 ARM 使用 svc #0 但系统调用号表不同。

1.1 系统调用的底层细节

从C函数调用到内核执行,系统调用经历了以下步骤:

用户态 C 函数 (open) 
→ libc 封装函数 (将 fd/路径/标志放入寄存器)
→ svc #0 (ARM64) / syscall (x86) 触发同步异常
→ 内核异常向量表 → el0_sync 处理
→ 根据系统调用号从 sys_call_table 查找内核函数
→ 执行内核中的 sys_open 函数
→ 复制用户空间参数 (copy_from_user)
→ 执行实际的文件打开操作
→ 返回结果
→ 恢复用户态寄存器上下文
→ 用户态继续执行

在 Android Bionic 中,系统调用封装位于 bionic/libc/arch-arm64/syscalls/。每个系统调用都是一个汇编函数,将参数移到正确的寄存器(x0-x5),将系统调用号放入 x8,然后执行 svc #0

二、文件 I/O:系统调用 vs C 库

Android NDK 中,文件 I/O 有两种方式:

2.1 无缓冲 I/O(系统调用)

#include <fcntl.h>
#include <unistd.h>

int fd = open("/sdcard/test.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
perror("open failed");
return;
}

const char *data = "Hello from native code\n";
ssize_t written = write(fd, data, strlen(data));
if (written < 0) {
perror("write failed");
}

// 移动文件指针
lseek(fd, 0, SEEK_SET);

char buffer[128];
ssize_t nread = read(fd, buffer, sizeof(buffer) - 1);
if (nread > 0) {
buffer[nread] = '\0';
}

close(fd);

openreadwriteclose 是系统调用,每次调用都涉及用户态到内核态的切换,开销较大。

2.2 缓冲 I/O(C 标准库)

#include <stdio.h>

FILE *fp = fopen("/sdcard/test.txt", "w+");
if (fp == NULL) {
perror("fopen failed");
return;
}

fprintf(fp, "Line %d\n", 1);
fputs("Hello from native\n", fp);

// 将缓冲区内容刷到磁盘
fflush(fp);

rewind(fp);

char line[256];
while (fgets(line, sizeof(line), fp) != NULL) {
printf("Read: %s", line);
}

fclose(fp);

FILE* 系列函数在用户态维护了一个缓冲区(默认 8KB),减少了系统调用的次数。但当需要精确的控制(如实时性要求高的场景、需要对文件描述符进行 poll/epoll/select 操作)时,应该使用无缓冲 I/O。

2.3 实用技巧:mmap 文件映射

#include <sys/mman.h>
#include <sys/stat.h>

int fd = open("/sdcard/large_file.bin", O_RDONLY);
struct stat st;
fstat(fd, &st);

// 将文件映射到虚拟地址空间
void *mapped = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (mapped == MAP_FAILED) {
perror("mmap failed");
close(fd);
return;
}

// 直接通过内存访问文件内容(无需 read/write 系统调用)
// 操作系统按需从磁盘加载页面(demand paging)
process_data((uint8_t *)mapped, st.st_size);

// 解除映射
munmap(mapped, st.st_size);
close(fd);

Android 的 ART 运行时加载 DEX/OAT 文件时使用 mmap 将其直接映射到进程空间,从而实现高效的代码和数据访问。Binder 驱动的数据传输也基于 mmap(一次性映射 1MB-16KB 的内核缓冲区,后续数据传输无需额外拷贝)。

源码路径:frameworks/native/libs/binder/ProcessState.cppmmap(NULL, BINDER_VM_SIZE, ...)

2.4 mmap 的深入:MAP_SHARED vs MAP_PRIVATE vs MAP_ANONYMOUS

// MAP_PRIVATE: 修改不写回文件(Copy on Write)
// 进程对映射区域的修改只影响本进程,不会写入文件
// 适合:加载只读文件、可执行文件、共享库
void *priv = mmap(NULL, file_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE, fd, 0);

// MAP_SHARED: 修改写回文件
// 多个进程映射同一文件时,修改对所有进程可见
// 适合:进程间共享内存
void *shared = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);

// MAP_ANONYMOUS: 不与文件关联(匿名映射)
// 内存初始化为零,fd 必须为 -1
// 适合:分配大块内存(替代 malloc),共享内存(fork 后子进程继承)
void *anon = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_SHARED, -1, 0);

// MAP_FIXED: 指定确切的映射地址(通常与 MAP_ANONYMOUS 合用)
// 小心:会覆盖已有映射,一般只在实现自定义内存分配器时使用

三、进程管理:Zygote 模型

Android 应用进程不是通过传统 fork+exec 创建的,而是通过 Zygote 进程fork 机制孵化,这极大地加速了应用启动。

init 进程
→ Zygote 进程 (app_process / system/bin/app_process64)
预加载 Framework 类、JNI 库、通用资源
→ fork() → 应用进程 1 (com.example.app1)
→ fork() → 应用进程 2 (com.example.app2)
→ fork() → 应用进程 3 (com.example.app3)

Zygote 的精妙之处在于 Linux fork 的 Copy-on-Write(CoW)特性:

  • fork() 后,子进程与父进程共享物理内存页(只读)。
  • 只有当子进程尝试写入某一页时,内核才会为该页创建一份副本。
  • 因此,Zygote 预加载的 Framework 代码和资源在所有应用进程之间共享,大大减少了整体内存占用。

在 NDK 中,fork() 可以直接使用,但 Android 对其有限制:

#include <unistd.h>
#include <sys/wait.h>

pid_t pid = fork();
if (pid == 0) {
// 子进程
// 注意:子进程只有一个线程(fork 调用者),其他线程不复制
// 子进程中不能安全使用大多数 Android API
execl("/system/bin/logcat", "logcat", "-d", NULL);
_exit(0); // 子进程使用 _exit 而非 exit
} else if (pid > 0) {
// 父进程
int status;
waitpid(pid, &status, 0); // 等待子进程结束
if (WIFEXITED(status)) {
printf("Child exited with %d\n", WEXITSTATUS(status));
}
} else {
perror("fork failed");
}

fork() 后子进程中的注意事项:

  1. 只有调用 fork() 的线程被复制到子进程。其它线程消失(可能导致持有的锁无法释放,造成死锁)。
  2. 子进程必须使用 _exit() 而不是 exit(),因为 exit() 会调用 atexit 注册的清理函数。
  3. 子进程不能安全地使用父进程的 JNIEnv(需要通过 AttachCurrentThread 重新获取)。

3.1 posix_spawn: fork + exec 的安全替代

fork 在多线程程序中是不安全的。posix_spawn 是更安全的替代方案:

#include <spawn.h>

pid_t child_pid;
char *argv[] = { "/system/bin/logcat", "-d", NULL };
extern char **environ;

int result = posix_spawn(&child_pid, "/system/bin/logcat",
NULL, NULL, argv, environ);
if (result == 0) {
int status;
waitpid(child_pid, &status, 0);
}

posix_spawn 的优势:直接在内核中创建新进程并加载新程序,避免了 fork 后到 exec 之间可能出现的竞争条件。

四、信号处理

信号(Signal)是 Linux 中进程间异步通知的机制。Android 的 tombstone(崩溃日志)就是由 debuggerd 捕获 crash 信号后生成的。

#include <signal.h>
#include <string.h>

void crash_handler(int sig, siginfo_t *info, void *ucontext) {
// 不要在信号处理器中做复杂操作(如 malloc、printf)
// 只记录关键信息,然后调用 _exit 或重新抛出信号

const char *sig_name = "UNKNOWN";
switch (sig) {
case SIGSEGV: sig_name = "SIGSEGV"; break;
case SIGABRT: sig_name = "SIGABRT"; break;
case SIGFPE: sig_name = "SIGFPE"; break;
case SIGILL: sig_name = "SIGILL"; break;
}

// 使用 write 系统调用输出(信号安全)
write(STDERR_FILENO, "Caught signal: ", 15);
write(STDERR_FILENO, sig_name, strlen(sig_name));
write(STDERR_FILENO, "\n", 1);

// 恢复默认信号处理器并重新触发(让系统生成 tombstone)
signal(sig, SIG_DFL);
raise(sig);
}

void install_crash_handler() {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = crash_handler;
sa.sa_flags = SA_SIGINFO | SA_ONSTACK; // SA_ONSTACK 使用备用栈

sigaction(SIGSEGV, &sa, NULL);
sigaction(SIGABRT, &sa, NULL);
sigaction(SIGFPE, &sa, NULL);
}

Android 的 debuggerd 守护进程(源码路径:system/core/debuggerd/)通过 ptrace attach 到 crash 的进程,读取寄存器、调用栈、内存映射等信息,生成 tombstone 文件(保存在 /data/tombstones/)。NDK 开发中分析 native crash 的首要工具就是 tombstone 和 ndk-stack。

信号处理器的限制:

  • 只能使用异步信号安全的函数(man 7 signal-safety)。
  • 不能调用 malloc/freeprintf/fprintfpthread_mutex_lock 等。
  • 安全的函数主要是:writereadopenclose_exitsignalraise
  • 推荐使用 SA_ONSTACK 标志,为信号处理器分配独立栈空间(避免栈溢出时信号处理器无法执行)。

4.1 signalfd:将信号变成文件描述符

传统的信号处理(signal handler)是异步的,不便于在事件循环中统一处理。Linux 的 signalfd 能将信号转换为一个文件描述符,纳入 epoll 等 I/O 复用框架:

#include <sys/signalfd.h>
#include <signal.h>
#include <unistd.h>

// 阻塞信号(防止默认处理)
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTERM);
sigprocmask(SIG_BLOCK, &mask, NULL);

// 创建 signalfd
int sfd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC);

// 在事件循环中处理
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sfd, &ev);

// 在 epoll_wait 返回后读取信号
struct signalfd_siginfo si;
ssize_t n = read(sfd, &si, sizeof(si));
if (n == sizeof(si)) {
printf("Received signal: %d\n", si.ssi_signo);
}

五、epoll:高效 I/O 多路复用

epoll 是 Linux 特有的 I/O 多路复用机制,Android 的 MessageQueue 和网络框架底层都依赖它(Java 层的 Looper → Native 层的 Looper::pollInnerepoll_wait):

#include <sys/epoll.h>

#define MAX_EVENTS 64

int epfd = epoll_create1(EPOLL_CLOEXEC); // 创建 epoll 实例
if (epfd < 0) {
perror("epoll_create1");
return;
}

struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET; // 读事件 + 边缘触发
ev.data.fd = server_fd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, server_fd, &ev) < 0) {
perror("epoll_ctl");
return;
}

// 事件循环
struct epoll_event events[MAX_EVENTS];
while (1) {
int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1); // 阻塞等待
if (nfds < 0) {
if (errno == EINTR) continue; // 被信号中断,继续
perror("epoll_wait");
break;
}

for (int i = 0; i < nfds; i++) {
if (events[i].events & EPOLLIN) {
// 可读
int fd = events[i].data.fd;
// 处理读事件
} else if (events[i].events & (EPOLLERR | EPOLLHUP)) {
// 错误或挂断 → 移除监听
epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
close(events[i].data.fd);
}
}
}
close(epfd);

epoll 的两种触发模式:

  • 水平触发(Level Triggered):只要 fd 有可读数据,每次 epoll_wait 都会返回该事件。类似 poll,编程简单。
  • 边缘触发(Edge Triggered):仅在 fd 状态从不可读变为可读时通知一次。需要循环 read 直到返回 EAGAIN,编程复杂但性能更好(减少重复通知)。

Android 的 android::Looper(Native 层)在 system/core/libutils/Looper.cpp 中,使用 epoll 监控文件描述符事件,同时支持定时器(通过 timerfd_create + epoll 实现)。

5.1 timerfd 和 eventfd

// timerfd: 将定时器变成文件描述符
#include <sys/timerfd.h>

// 创建非阻塞 timerfd
int tfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);

// 设置定时器:1 秒后触发,之后每 500ms 触发一次
struct itimerspec its;
its.it_value.tv_sec = 1;
its.it_value.tv_nsec = 0;
its.it_interval.tv_sec = 0;
its.it_interval.tv_nsec = 500 * 1000 * 1000; // 500ms
timerfd_settime(tfd, 0, &its, NULL);

// 纳入 epoll 事件循环
// 触发时 read(tfd, &expirations, sizeof(uint64_t))
// expirations 表示触发了多少次

// eventfd: 线程/进程间的轻量级事件通知
#include <sys/eventfd.h>
int efd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);

// 发送事件
uint64_t val = 1;
write(efd, &val, sizeof(val));

// 接收事件
read(efd, &val, sizeof(val)); // val 包含累积的计数值

六、Unix Domain Socket

Unix Domain Socket 是同一台设备上进程间通信(IPC)的高效方式。相比 TCP/IP,它不需要经过网络协议栈,性能更高(延迟更低,吞吐更大)。

#include <sys/socket.h>
#include <sys/un.h>

// 服务端
int server_fd = socket(AF_UNIX, SOCK_STREAM, 0);

struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, "/data/local/tmp/my_socket", sizeof(addr.sun_path) - 1);
unlink(addr.sun_path); // 删除可能残留的 socket 文件

bind(server_fd, (struct sockaddr *)&addr, sizeof(addr));
listen(server_fd, 5);

int client_fd = accept(server_fd, NULL, NULL);

char buffer[1024];
ssize_t n = recv(client_fd, buffer, sizeof(buffer), 0);
// 处理请求
send(client_fd, "OK", 2, 0);

close(client_fd);
close(server_fd);
unlink(addr.sun_path);

Android 系统服务的 IPC 通常使用 Binder,但在一些性能敏感的场景(如 SurfaceFlinger 与 App 的 BufferQueue 通信、mediaserver 的音频数据传输)会使用 Unix Domain Socket 或匿名共享内存(ashmem)。

Android 的 installd 守护进程(包管理器后台服务)通过 Unix Domain Socket 接收 pm 命令:

installd → /dev/socket/installd (Unix Domain Socket)
→ install / uninstall / dexopt 等操作

6.1 SCM_RIGHTS:通过 Unix Socket 传递文件描述符

// 通过 Unix Socket 在进程间传递文件描述符
// (Android Binder 中也使用了类似机制传递文件描述符)

// 发送端
struct msghdr msg = {0};
struct iovec iov[1];
char data[1] = {0};
int fd_to_send = open("/path/to/file", O_RDONLY);

// 设置控制消息(携带文件描述符)
union {
struct cmsghdr hdr;
char buf[CMSG_SPACE(sizeof(int))];
} cmsg;

msg.msg_control = cmsg.buf;
msg.msg_controllen = sizeof(cmsg.buf);
msg.msg_iov = iov;
msg.msg_iovlen = 1;
iov[0].iov_base = data;
iov[0].iov_len = sizeof(data);

struct cmsghdr *cmsgp = CMSG_FIRSTHDR(&msg);
cmsgp->cmsg_level = SOL_SOCKET;
cmsgp->cmsg_type = SCM_RIGHTS;
cmsgp->cmsg_len = CMSG_LEN(sizeof(int));
memcpy(CMSG_DATA(cmsgp), &fd_to_send, sizeof(int));

sendmsg(socket_fd, &msg, 0);

// 接收端
struct msghdr rmsg;
// ... 类似结构 ...
recvmsg(socket_fd, &rmsg, 0);

int received_fd;
memcpy(&received_fd, CMSG_DATA(CMSG_FIRSTHDR(&rmsg)), sizeof(int));
// 现在 received_fd 指向与发送端相同的文件

七、匿名共享内存(ashmem)

Android 特有的 ashmem 驱动提供了进程间的匿名共享内存:

#include <sys/mman.h>
#include <linux/ashmem.h>
#include <sys/ioctl.h>

// 创建匿名共享内存
int fd = open("/dev/ashmem", O_RDWR);
if (fd < 0) {
perror("open /dev/ashmem");
return;
}

// 设置名称(用于调试)和大小
ioctl(fd, ASHMEM_SET_NAME, "MySharedBuffer");
ioctl(fd, ASHMEM_SET_SIZE, 4096);

// 映射到进程空间
void *shared_mem = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

// 可以将 fd 通过 Binder 传递给其他进程
// 其他进程使用同一 fd 或 dup 后的 fd 做 mmap

// 清理
munmap(shared_mem, 4096);
close(fd);

Android 的 MemoryFile(Java API)和 IMemory(Binder 接口)底层都是对 ashmem 的封装。ashmem 的特性是:内核通过引用计数跟踪共享内存的引用者,当所有引用者释放后自动回收内存。

7.1 System V IPC vs POSIX IPC

Linux 提供了两套 IPC 机制:

机制 System V POSIX 说明
共享内存 shmget/shmat/shmdt shm_open/mmap POSIX 更易用
信号量 semget/semop sem_open/sem_wait POSIX 支持命名和匿名
消息队列 msgsnd/msgrcv mq_send/mq_receive POSIX 支持优先级

Android Bionic 全面支持 POSIX IPC,但对 System V IPC 的支持不完整(部分函数为 stub)。在 Android NDK 开发中,优先使用 POSIX IPC。

// POSIX 命名共享内存
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>

int shm_fd = shm_open("/my_shm", O_CREAT | O_RDWR, 0600);
ftruncate(shm_fd, 4096);
void *shm = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
// ... 使用 shm ...
munmap(shm, 4096);
close(shm_fd);
shm_unlink("/my_shm");

八、CMake 构建配置

现代 Android NDK 项目使用 CMake 作为构建系统:

# CMakeLists.txt
cmake_minimum_required(VERSION 3.18.1)
project("nativeutils")

# 查找 NDK 提供的库
find_library(log-lib log) # liblog.so (__android_log_print)
find_library(z-lib z) # libz.so (压缩)

# 构建动态库
add_library(nativeutils SHARED
src/main/cpp/jni_main.cpp
src/main/cpp/crypto/aes_encrypt.cpp
src/main/cpp/ipc/socket_server.cpp
src/main/cpp/ipc/socket_client.cpp
)

# 包含头文件目录
target_include_directories(nativeutils PRIVATE
src/main/cpp/include
${CMAKE_CURRENT_SOURCE_DIR}/third_party/openssl/include
)

# 链接库
target_link_libraries(nativeutils
${log-lib}
${z-lib}
android # libandroid.so (AAssetManager 等)
openssl # 第三方预编译库
)

# 编译选项
target_compile_options(nativeutils PRIVATE
-Wall
-Werror
-O2
-DANDROID
)

app 模块的 build.gradle 中配置:

android {
defaultConfig {
ndk {
abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64'
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
version "3.18.1"
}
}
}

九、面试常问题目

Q1: mmap 和 read/write 的区别?什么场景下用 mmap?

read/write 需要将数据从内核缓冲区拷贝到用户空间(一次数据拷贝)。mmap 将文件映射到进程的虚拟地址空间,通过缺页中断按需加载数据,不需要显式的 read/write 调用。mmap 适合:(1) 大文件的随机访问(只需访问其中一部分);(2) 多个进程共享同一文件的数据(MAP_SHARED);(3) 零拷贝的数据传输(如 Binder)。read/write 适合小文件、顺序读写。Android 的 DEX/OAT 加载使用 mmap。

Q2: epoll 的水平触发和边缘触发有什么区别?

水平触发(LT):只要 fd 仍有未处理的数据,每次 epoll_wait 都会返回该 fd 的事件。编程简单,与 select/poll 行为一致。边缘触发(ET):只在 fd 状态变化时(如从不可读变为可读)通知一次。必须循环读取直到返回 EAGAIN,否则可能丢失事件。ET 模式的优点是可以避免重复通知,减少 epoll_wait 的调用次数,适合高并发场景。Android Native Looper 默认使用 LT 模式。

Q3: Zygote fork 为什么比直接创建进程快?

Zygote 进程在启动时预加载了 Framework 类、JNI 库、系统资源(主题、字体等)。fork 使用 Copy-on-Write 机制,子进程共享这些预加载的资源,不需要重新加载。直接创建进程(fork + exec)需要一个全新的内存空间初始化过程,要重新执行所有加载步骤。Zygote 模式下,应用启动只需 fork(复制页表,约几毫秒),然后在新进程中初始化自己的少量组件。

Q4: Unix Domain Socket 和 TCP/IP Socket 的区别?在 Android 中如何选择?

Unix Domain Socket 用于同一台设备上的进程间通信,不需要经过 IP 层和 TCP 协议栈,数据在内核中直接传输(不经过网络设备),延迟更低、吞吐更高。TCP Socket 用于网络通信,可跨设备。在 Android 中,同一设备上的 IPC 首选 Binder(有权限管理、引用计数等高级特性),但当需要流式数据传输(如音频流)或需要支持非 Java 进程时,Unix Domain Socket 是合适的补充方案。

Q5: timerfd 和 eventfd 相比传统的定时器和条件变量有什么优势?

timerfd 和 eventfd 将定时器和事件通知统一为文件描述符,可以无缝纳入 epoll/select 等 I/O 多路复用框架中。这意味着一个线程可以用同一个 epoll_wait 同时等待:socket 数据到达 + 定时器超时 + 事件通知,避免了传统的 poll(timeout) + 定时器回调的复杂性。Android 的 Native Looper 正是使用 timerfd 来支持定时消息。eventfd 可用于替代条件变量,在单生产者单消费者场景下,eventfd 的性能优于条件变量(因为在内核中实现,无需用户态的 mutex 保护)。


参考源码路径:

  • Android Looper(Native):system/core/libutils/Looper.cpp
  • Zygote 进程:frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
  • Zygote fork 流程:frameworks/base/core/jni/com_android_internal_os_Zygote.cpp
  • Binder mmap:frameworks/native/libs/binder/ProcessState.cpp
  • debuggerd:system/core/debuggerd/
  • installd:frameworks/native/cmds/installd/
  • ashmem 驱动:kernel/common/drivers/staging/android/ashmem.c
  • Bionic epoll:bionic/libc/bionic/epoll_create.cpp
打赏
  • 微信
  • 支付宝

评论